﻿/**
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QDrive.QBClient;
using QDrive.Domain;
using System.Xml.Linq;
using System.Xml;

namespace QDrive
{
    /**
     * QuickBase APIs as required by QDrive
     * 
     */
    class QBAPI
    {
        public QuickBaseClient client;

        public QBAPI(QuickBaseClient client)
        {
            this.client = client;
            this.client.Authenticate(true);
        }

        /**
         * @brief
         * Extract the CSV content from a report
         * 
         * @param report - the report to query
         * 
         * Example: https://www.quickbase.com/db/DBID?a=q&qid=36&dlta=xs%7E
         */
        public String DownloadCSV(Report report) {
            if (report.tableId == null || string.IsNullOrEmpty(report.qid)) 
                return "";
            client.SelectDbid(report.tableId.id);
            return client.GetHandlerAsString("?a=q&qid=" + report.qid + "&dlta=xs%7E");
        }

        /**
         * Retrieve a list of tables based on the application identifier
         */
        public IEnumerable<DBID> GetTables(String appID)
        {
            var doc = client.getSchema(appID);
            var tables = from el in doc.Descendants("chdbid")
                       select new DBID()
                       {
                           id = el.Value.ToString(),
                           label = el.Attribute("name").Value.ToString().Replace("_dbid_", "")
                       };
            return tables;
        }

        /**
         * @brief
         * Get a list of reports from the specific table and fields
         * 
         * @param dbid - table identifier to list the reports
         * @param [out] fields - list of fields associated with the reports
         */
        public IEnumerable<Report> GetReports(String dbid, List<QBField> fields)
        {
            var doc = client.getSchema(dbid);
            var reports = from el in doc.Descendants("query")
                          select new Report()
                          {
                              label = el.Element("qyname").Value.ToString(),
                              type = el.Element("qytype").Value.ToString(),
                              qid = el.Attribute("id").Value.ToString()
                          };
            var _flist = from el in doc.Descendants("field")
                             select new QBField()
                             {
                                 id = el.Attribute("id").Value.ToString(),
                                 name = el.Element("label").Value.ToString(),
                                 type = el.Attribute("field_type").Value.ToString()
                             };
            foreach (QBField f in _flist) 
                fields.Add(f);
            return reports;
        }

        /**
         * @brief
         * Get a list of applications the user has access to via API_GrantedDBs
         *
         */
        public IEnumerable<DBID> GetApplications()
        {
            var doc = client.getGrantedApps();
            int errcode = Int32.Parse(doc.Element("qdbapi").Element("errcode").Value);
            if (0 == errcode)
            {
                var apps = from el in doc.Descendants("dbinfo")
                           select new DBID()
                           {
                               id = el.Element("dbid").Value.ToString(),
                               label = el.Element("dbname").Value.ToString()
                           };
                return apps;
            }
            //doc.

            return null;
        }

        /**
         * @brief
         * Retrieve a list of file attachment fields for a table
         * 
         * @param tableID - dbid of a table
         * @param fields - list of fields containing file attachments
         */
        public List<FileAttachment> GetFileNames(string tableId, List<QBField> fields)
        {
            List<FileAttachment> list = new List<FileAttachment>();
            client.SelectDbid(tableId);
            String clist = null;
            foreach (QBField field in fields) {
                if (!string.IsNullOrEmpty(clist))
                    clist += "." + field.id;
                else
                    clist = field.id;
            }
            // fid 3 is the record id #
            String result = client.GetHandlerAsString("?a=q&query={3.NE.''}&clist=3." + clist + "&dlta=xs%7E");
            int lineNum = 0, recordID = 0;
            foreach (String line in result.Split('\n'))
            {
                if (lineNum++ != 0)
                {
                    // The first value is the record ID
                    int c = 0;
                    foreach (String val in line.Split(','))
                    {
                        if (c == 0)
                        {
                            Int32.TryParse(val, out recordID);
                        }
                        else
                        {
                            FileAttachment attachment = new FileAttachment();
                            attachment.recordId = recordID;
                            attachment.name = val;
                            attachment.field = fields[c - 1];
                            attachment.tableId = tableId;
                            list.Add(attachment);
                        }
                        c++;
                    }
                }
            }
            return list;
        }

        /**
         * @brief
         * Download the associated file attachment as a single byte array.  This clearly won't work for larger files...
         * 
         */
        public byte[] GetFile(FileAttachment attachment)
        {
            client.SelectDbid(attachment.tableId);
            byte[] content = client.GetHandlerAsByteArray("/up/" + attachment.tableId + "/a/r" + attachment.recordId + "/e" + attachment.field.id + "/v0");
            return content;
        }

        /**
         * @brief 
         * Upload a file to a QuickBase field
         * 
         * @param tableID - the table to upload
         * @param byte[] - the file content (all of the content must be uploadable in a single pass as its base64 encoded to the API_AddRecord call
         * @param filename - name of the file to define within the specified field.
         */
        public int UploadFile(DBID tableId, QBField field, byte[] buffer, String filename)
        {
            StringBuilder sb = new StringBuilder("<qdbapi><field fid=\"");
            sb.Append(field.id);
            sb.Append("\" filename=\"");
            sb.Append(filename);
            sb.Append("\">");
            sb.Append(Convert.ToBase64String(buffer));
            sb.Append("</field></qdbapi>");
            var doc = client.PostAPIURL(tableId.id, "API_AddRecord", sb.ToString());
            var ec = doc.Descendants().Where<XElement>(delegate(XElement p)
            {
                return ((p.NodeType == XmlNodeType.Element) && (p.Name.LocalName == "errcode"));
            }).Select<XElement, string>(delegate(XElement p)
            {
                return p.Value;
            }).FirstOrDefault<string>();
            int errorCode = Int32.Parse(ec);
            return errorCode;
        }

        /**
         * @brief
         * Retrieve the file size by asking for HTTP HEAD via file download
         * 
         * @param file
         */
        public long GetFileSize(FileAttachment file)
        {
            try
            {
                if (null != file)
                {
                    client.SelectDbid(file.tableId);
                    long size = client.GetHeadSize("/up/" + file.tableId + "/a/r" + file.recordId + "/e" + file.field.id + "/v0");
                    return size;
                }
            }
            catch (Exception) { 
                // should retry...
            }
            return 0;
        }
    }
}
